import {Rule} from "@khanacademy/perseus-linter";
import {debounce} from "underscore";

/**
 * TranslationLinter asynchronously calls Khan Academy's poentry linter.
 *
 * The poentry linter checks for issues that prevent content from being
 * translated. This linter is KA-specific and so TranslationLinter does not do
 * anything in non-KA environments.
 */

declare const KA: any;

// A LintCB is called once the linter has been run.
type LintCB = (errors: ReadonlyArray<string>) => void;

// msec to wait before we actually call the linter after receiving the
// last call. The timer is reset if the lint is called during the wait.
const DEBOUNCE_TIMEOUT = 1000;

export default class TranslationLinter {
    // To avoid linting when nothing has changed.
    previousContent: string | null | undefined = null;

    /**
     * Runs the poentry linter on the given perseus string asynchronously.
     *
     * It doesn't do anything unless we're in a KA environment.
     *
     * onLintErrorsGenerated is called even if there are no errors. It is not
     * called if perseusStr is unchanged from the last call.
     *
     * Linting is relatively expensive -- so we debounce linting each instance
     * for some amount of time.
     */
    runLinter: any = debounce(
        (perseusStr: string, onLintErrorsGenerated: LintCB) => {
            // TODO(FEI-5003): Do we still need to check for 'KA'?
            if (typeof KA === "undefined") {
                return;
            }

            if (perseusStr === this.previousContent) {
                return;
            }

            this.previousContent = perseusStr;

            if (perseusStr === "") {
                onLintErrorsGenerated([]);
                return;
            }

            fetch("/api/internal/translations/lint_poentry?preview=1&lang=en", {
                headers: {"Content-Type": "application/json"},
                body: JSON.stringify({
                    // The poentry linter verifies that the translation from
                    // the original text to the translated text is sane. We
                    // want to ensure that the translation from English to
                    // English is sane, so msgid === mstr.
                    msgid: perseusStr, // msgid is the original text
                    msgstr: perseusStr, // msgstr is the translated text
                    format: "perseus_text",
                    filename: "",
                }),
                method: "POST",
            })
                .then(
                    (response) => {
                        if (response.status >= 400) {
                            return {
                                status: "error",
                                message: "Could not run i18n linter.",
                            };
                        }
                        return response.json();
                    },
                    (rejection) => {
                        return {
                            status: "error",
                            message: "Could not run i18n linter.",
                        };
                    },
                )
                .then((json) => {
                    if (json.status === "error") {
                        onLintErrorsGenerated([
                            "Some part of this text makes it untranslatable. " +
                                "The specific message from the i18n linter was: " +
                                json.message.replace(/\n/g, " "),
                        ]);
                    } else {
                        onLintErrorsGenerated([]);
                    }
                });
        },
        DEBOUNCE_TIMEOUT,
    );

    /**
     * Applies an array of errors generated by linters without position
     * information (TranslationLinter and the legacy getSaveWarnings()) to the
     * top of a Perseus tree.
     */
    applyLintErrors(
        parsedMarkdown: any,
        translationLintErrors: ReadonlyArray<string>,
    ) {
        // These lint errors do not have position data associated with
        // them, so we just plop them at the top.
        // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
        if (translationLintErrors.length) {
            const errorText = translationLintErrors.join("\n\n");
            parsedMarkdown.unshift({
                content: {
                    type: "text",
                    content: "",
                },
                insideTable: false,
                message: errorText,
                ruleName: "legacy-error",
                severity: Rule.Severity.ERROR,
                type: "lint",
            });
        }
    }
}
